#pragma once

#include "Utils.h"
#include "videoWindow.h"
#include "eventNotification.h"
#include <memory>
#include <DShow.h>


namespace DSP
{
	#define ONE_SECOND 10000000
	#define VOLUME_MIN -10000
	#define USER_VOLUME_MAX 100

	template <typename T>
	class DSplayer
	{
	private:
		std::auto_ptr<T> Fgraph;
		ATL::CComPtr<IMediaControl> pControl;
		ATL::CComPtr<IMediaSeeking> pSeek;
		ATL::CComPtr<IBasicAudio> pAudio;
		std::auto_ptr<Ivideo> pVideo;
		ATL::CComPtr<IMediaEvent> pEvent;
		std::auto_ptr<Ievent> eventNotif;
		void seeking(AM_SEEKING_SeekingCapabilities sCap,
					 REFERENCE_TIME& pCurrent,
					 AM_SEEKING_SeekingFlags curFlag,
					 REFERENCE_TIME& pStop,
					 AM_SEEKING_SeekingFlags stopFlag);
	public:
		DSplayer(){}
		DSplayer(T* pGraph);
		~DSplayer(void);
		void play(void);
		void pause(void);
		void stop(void);
		void seek(LONGLONG pos);
		void getDuration(LONGLONG& pos);
		void setVolume(long vol);
		void getVolume(long& vol);
		void getCurrentPosition(LONGLONG& pos);
		Ivideo* getVideo();
		Ievent* getMediaEvent(pFunc_void_long funcEventProcess, pFunc_void_rConstException funcErrorProcess);
	};


	template <typename T>
	DSplayer<T>::DSplayer(T* pGraph)
	{
		Fgraph.reset(pGraph);
		Fgraph->getInterface(pControl);
		Fgraph->getInterface(pSeek);
		Fgraph->getInterface(pAudio);
		ATL::CComPtr<IBasicVideo> pBasicVideo;
		Fgraph->getInterface(pBasicVideo);
		long width, height;
		HRESULT hr = pBasicVideo->GetVideoSize(&width, &height);
		if(SUCCEEDED(hr))
		{
			ATL::CComPtr<IVideoWindow> pVideoWindow;
			Fgraph->getInterface(pVideoWindow);
			pVideo.reset(new videoWindow(pBasicVideo, pVideoWindow));
		}
		Fgraph->getInterface(pEvent);
	}


	template <typename T>
	DSplayer<T>::~DSplayer(void)
	{
	}


	template <typename T>
	void DSplayer<T>::play(void)
	{
		HRESULT hr = pControl->Run();
		if(FAILED(hr))
			throw errorInfo("Error: Can not run the graph.", hr);
	}


	template <typename T>
	void DSplayer<T>::pause(void)
	{
		HRESULT hr = pControl->Pause();
		if(FAILED(hr))
			throw errorInfo("Error: Can not paused graph.", hr);
	}


	template <typename T>
	void DSplayer<T>::stop(void)
	{
		HRESULT hr = pControl->Pause();
		if(FAILED(hr))
			throw errorInfo("Error: Can not paused graph.", hr);
		REFERENCE_TIME cur = 0, stop = 0;
		seeking(AM_SEEKING_CanSeekBackwards, cur, AM_SEEKING_AbsolutePositioning, stop, AM_SEEKING_NoPositioning);
		hr = pControl->Stop();
		if(FAILED(hr))
			throw errorInfo("Error: Can not stopped graph.", hr);
	}


	template <typename T>
	void DSplayer<T>::seeking(AM_SEEKING_SeekingCapabilities sCap,
							  REFERENCE_TIME& pCurrent,
							  AM_SEEKING_SeekingFlags curFlag,
							  REFERENCE_TIME& pStop,
							  AM_SEEKING_SeekingFlags stopFlag)
	{
		DWORD cap = 0;
		HRESULT hr = pSeek->GetCapabilities(&cap);
		if(FAILED(hr))
			throw errorInfo("Error: Can not get capabilities.", hr);
		if(cap & sCap)
		{
			hr = pSeek->SetPositions(&pCurrent, curFlag, &pStop, stopFlag);
			if(FAILED(hr))
				throw errorInfo("Error: Can not set position.", hr);
		}
	}


	template <typename T>
	void DSplayer<T>::seek(LONGLONG pos)
	{
		REFERENCE_TIME cur = pos*ONE_SECOND, stop = 0;
		seeking(AM_SEEKING_CanSeekAbsolute, cur, AM_SEEKING_AbsolutePositioning, stop, AM_SEEKING_NoPositioning);
	}


	template <typename T>
	void DSplayer<T>::getDuration(LONGLONG& pos)
	{
		HRESULT hr = pSeek->GetDuration(&pos);
		if(FAILED(hr))
			throw errorInfo("Error: Can not get maximum position.", hr);
		pos /= ONE_SECOND;
	}


	template <typename T>
	void DSplayer<T>::setVolume(long vol)
	{
		HRESULT hr = pAudio->put_Volume(conversionFunction(vol, USER_VOLUME_MAX, -VOLUME_MIN));
		if(FAILED(hr))
			throw errorInfo("Error: Can not set the volume.", hr);
	}


	template <typename T>
	void DSplayer<T>::getVolume(long& vol)
	{
		HRESULT hr = pAudio->get_Volume(&vol);
		if(FAILED(hr))
			throw errorInfo("Error: Can not get volume.", hr);
	}


	template <typename T>
	void DSplayer<T>::getCurrentPosition(LONGLONG& pos)
	{
		HRESULT hr = pSeek->GetCurrentPosition(&pos);
		if(FAILED(hr))
			throw errorInfo("Error: Can not get current position.", hr);
		pos /= ONE_SECOND;
	}
	

	template<typename T>
	Ivideo* DSplayer<T>::getVideo()
	{
		return pVideo.get();
	}


	template <typename T>
	Ievent* DSplayer<T>::getMediaEvent(pFunc_void_long funcEventProcess, pFunc_void_rConstException funcErrorProcess)
	{
		eventNotif.reset(new eventNotification(pEvent, funcEventProcess, funcErrorProcess));
		return eventNotif.get();
	}
}